/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ssp.database;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import ssp.net.NWcomponent;
import ssp.service.FrontendServer;
import ssp.service.FrontendServerManager;
import ssp.topology.Node;
import ssp.utility.Pair;

/**
 * ネットワークマップを表すクラス
 * @author yamazaki
 */
public class NetworkMap implements Database {
    
    /**
     * 最後にデータベースを更新した時刻
     */
    private double lastUpdateTime;
    
    /**
     * 通信帯域の最小単位を保持するマップ<br>
     *  Pair&lt;NWcomponent.ID, connectedNWcomponents&gt;がkey値
     */
    private Map<Pair<Integer, Set<Node>>, Double> bandwidthUnitTable;
    
    /**
     * QoS指標のリストを保持するマップ<br>
     * Pair&lt;NWcomponent.ID, connectedNWcomponents&gt;がkey値
     */
    private Map<Pair<Integer, Set<Node>>, List<Integer>> QoSindexListTable;
    
    /**
     * 許容可能コネクション数を保持するマップ<br>
     * Pair&lt;NWcomponent.ID, connectedNWcomponents&gt;がkey値
     */
    private Map<Pair<Integer, Set<Node>>, Integer> capacityTable;
    
    /**
     * 残り許容可能数コネクション数を保持するマップ<br>
     * Pair&lt;NWcomponent.ID, connectedNWcomponents&gt;がkey値
     */
    private Map<Pair<Integer, Set<Node>>, Integer> availabilityTable;
    
    /**
     * 各ネットワークコンポーネントからフロントエンドサーバまでのホップ数を保持するマップ
     */
    private Map<Pair<Integer, FrontendServer>, Integer> hopCountTable;
    
    /**
     * コンストラクタ
     */
    public NetworkMap() {
        init();
    }

    @Override
    public final void init() {
        this.lastUpdateTime = 0.0;
        this.bandwidthUnitTable = new HashMap<Pair<Integer, Set<Node>>, Double>();
        this.QoSindexListTable = new HashMap<Pair<Integer, Set<Node>>, List<Integer>>();
        this.capacityTable = new HashMap<Pair<Integer, Set<Node>>, Integer>();
        this.availabilityTable = new HashMap<Pair<Integer, Set<Node>>, Integer>();
        this.hopCountTable = new HashMap<Pair<Integer, FrontendServer>, Integer>();
    }

    /**
     * @return the lastUpdateTime
     */
    public double getLastUpdateTime() {
        return lastUpdateTime;
    }
    
    /**
     * @param key
     * @return the bandwidthUnit
     */
    public double getBandwidthUnit(Pair<Integer, Set<Node>> key) {
        return this.bandwidthUnitTable.get(key);
    }
    
    /**
     * @param key
     * @param index
     * @return the QoSindex
     */
    public int getQoSindex(Pair<Integer, Set<Node>> key, int index) {
        return this.QoSindexListTable.get(key).get(index);
    }
    
    /**
     * @param key
     * @return the capacity
     */
    public int getCapacity(Pair<Integer, Set<Node>> key) {
        return this.capacityTable.get(key);
    }
    
    /**
     * @param key
     * @return the availability
     */
    public int getAvailability(Pair<Integer, Set<Node>> key) {
        return this.availabilityTable.get(key);
    }
    
    /**
     * 指定のネットワークコンポーネントからnホップ以内のフロントエンドサーバのリストを返す
     * @param srcNodeID ネットワークコンポーネントのID
     * @param hopNum ホップ数
     * @return srcNodeからhopNum以内のフロントエンドサーバリスト
     */
    public List<FrontendServer> getNearbyServer(int srcNodeID, int hopNum) {
        List<FrontendServer> frontendServerList = new ArrayList<FrontendServer>();
        for(Entry<Pair<Integer, FrontendServer>, Integer> entry : hopCountTable.entrySet()) {
            if(entry.getKey().getFirst() == srcNodeID && entry.getValue() < hopNum) {
                frontendServerList.add(entry.getKey().getSecond());
            }
        }
        return frontendServerList;
    }
    
    /**
     * bandwidthUnitTableを更新する
     * @param currentTime 更新時刻
     * @param key
     * @param val 更新値
     */
    public void updateBandwidthUnitTable(double currentTime, Pair<Integer, Set<Node>> key, double val) {
        this.bandwidthUnitTable.put(key, val);
        this.lastUpdateTime = currentTime;
    }
    
    /**
     * QoSindexListTableを更新する
     * @param currentTime 更新時刻
     * @param key
     * @param val 更新値
     */
    public void updateQoSindexListTable(double currentTime, Pair<Integer, Set<Node>> key, List<Integer> val) {
        this.QoSindexListTable.put(key, new ArrayList<Integer>(val));
        this.lastUpdateTime = currentTime;
    }
    
    /**
     * capacityTableを更新する
     * @param currentTime 更新時刻
     * @param key
     * @param val 更新値
     */
    public void updateCapacityTable(double currentTime, Pair<Integer, Set<Node>> key, int val) {
        this.capacityTable.put(key, val);
        this.lastUpdateTime = currentTime;
    }
    
    /**
     * availabilityTableを更新する
     * @param currentTime 更新時刻
     * @param key
     * @param availability 更新値
     */
    public void updateAvailabilityTable(double currentTime, Pair<Integer, Set<Node>> key, int availability) {
        this.availabilityTable.put(key, availability);
        this.lastUpdateTime = currentTime;
    }
    
    /**
     * 指定のネットワークコンポーネントから各フロントエンドサーバまでのホップ数を更新する
     * @param currentTime 更新時刻
     * @param srcNode 起点ネットワークコンポーネント
     */
    public void updateHopCountTable(double currentTime, NWcomponent srcNode) {
        
        //未探索リスト
        Map<FrontendServer, NWcomponent> open = new HashMap<FrontendServer, NWcomponent>();
        for(FrontendServer frontendServer : FrontendServerManager.getINSTANCE().getFrontendServerList()) {
            open.put(frontendServer, (NWcomponent)frontendServer.getConnectedNWcomponent());
        }
        
        //探索済みネットワークコンポーネント
        List<NWcomponent> close = new ArrayList<NWcomponent>();
        
        //探索中ネットワークコンポーネント
        List<NWcomponent> search = new ArrayList<NWcomponent>();
        
        //次探索ネットワークコンポーネント
        List<NWcomponent> nextSearch = new ArrayList<NWcomponent>();
        
        search.add(srcNode);
        
        //ホップ数
        int metric = 0;
        
        //探索すべきネットワークコンポーネントが空になるまで
        while (!search.isEmpty()) {
            for (NWcomponent dstNode : search) {
                
                //退避リスト
                List<FrontendServer> tmp = new ArrayList<FrontendServer>();
                
                //探索中のネットワークコンポーネントが未探索リストに含まれていればhopCountTableを更新
                for (Entry<FrontendServer, NWcomponent> entry : open.entrySet()) {
                    if (dstNode.equals(entry.getValue())) {
                        tmp.add(entry.getKey());
                        this.hopCountTable.put(new Pair<Integer, FrontendServer>(srcNode.getID(), entry.getKey()), metric);
                    }
                }
                
                //未探索リストを更新
                for (FrontendServer searched : tmp) {
                    open.remove(searched);
                }
                
                //探索済みに追加
                close.add(dstNode);
                
                //探索中ネットワークコンポーネントの隣接を次探索リストに追加
                for (Node neighbor : dstNode.getNeighborNWcomponents()) {
                    if(!close.contains((NWcomponent)neighbor) && !search.contains((NWcomponent)neighbor) && !nextSearch.contains((NWcomponent)neighbor)){
                        nextSearch.add((NWcomponent) neighbor);
                    }
                }
                
            }
            
            //次ループのために更新
            search = new ArrayList<NWcomponent>(nextSearch);
            nextSearch.clear();
            metric++;
            
        }
        
        this.lastUpdateTime = currentTime;
    }
    
}
